﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;

namespace Loki.Model
{
	public class EntityLinkContainer<TEntityKey> : LokiObject, IDisposable, IContainer<IEntityLink<TEntityKey>>, IEntityLinkContainer<TEntityKey>
	{
		#region Disposable
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="EntityContainer" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="EntityContainer" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~EntityLinkContainer()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="EntityContainer" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{

		}
		#endregion

		private static NotifyCollectionChangedEventArgs _ResetArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);

		private LinkContainerConfiguration<TEntityKey> _Config;
		private bool _Loaded = false;
		private object _Lock = new object();
		private Func<IEnumerable<IEntityLink<TEntityKey>>> _FullLoader;
		private Func<TEntityKey, IEnumerable<IEntityLink<TEntityKey>>> _LeftLoader;
		private Func<TEntityKey, IEnumerable<IEntityLink<TEntityKey>>> _RightLoader;

		private UniqueKeyMapper<IEntityLink<TEntityKey>, IEntityLink<TEntityKey>> _PK;
		private IndexedKeyMapper<IEntityLink<TEntityKey>, TEntityKey> _LeftIndex;
		private IndexedKeyMapper<IEntityLink<TEntityKey>, TEntityKey> _RightIndex;

		public EntityLinkContainer(LinkContainerConfiguration<TEntityKey> P_Config)
		{
			_Config = P_Config;
			LinkEqualityComparer<TEntityKey> _Comparer = new LinkEqualityComparer<TEntityKey>();

			_FullLoader = _Config.FullLoader.Compile();
			_LeftLoader = _Config.LeftLoader.Compile();
			_RightLoader = _Config.RightLoader.Compile();

			_LeftIndex = new IndexedKeyMapper<IEntityLink<TEntityKey>, TEntityKey>(
				new IndexConfiguration<IEntityLink<TEntityKey>, TEntityKey>()
				{
					Indexer = x => x.Left,
					Loader = x => _LeftLoader((TEntityKey)x)
				}, _Comparer);

			_RightIndex = new IndexedKeyMapper<IEntityLink<TEntityKey>, TEntityKey>(
				new IndexConfiguration<IEntityLink<TEntityKey>, TEntityKey>()
				{
					Indexer = x => x.Right,
					Loader = x => _RightLoader((TEntityKey)x)
				}, _Comparer);

			_PK = new UniqueKeyMapper<IEntityLink<TEntityKey>, IEntityLink<TEntityKey>>(
				x => x, _Comparer);
		}

		public void FullLoad()
		{
			lock (_Lock)
			{
				_EnsureFullLoad();
			}
		}

		public void Reset()
		{
			lock (_Lock)
			{
				_Reset();
			}
		}

		private void _Reset()
		{
			_PK.Reset();
			_LeftIndex.Reset();
			_RightIndex.Reset();
			_Loaded = false;

			OnCollectionChanged(_ResetArgs);
		}

		public IEnumerable<IEntityLink<TEntityKey>> GetAllByLeft(TEntityKey P_LeftValue)
		{
			lock (_Lock)
			{
				_EnsureLeftLoaded(P_LeftValue);
				return _LeftIndex.GetAllByKey(P_LeftValue);
			}
		}

		public IEnumerable<IEntityLink<TEntityKey>> GetAllByRight(TEntityKey P_RightValue)
		{
			lock (_Lock)
			{
				_EnsureRightLoaded(P_RightValue);
				return _RightIndex.GetAllByKey(P_RightValue);
			}
		}

		public bool Register(IEntityLink<TEntityKey> P_Entity)
		{
			lock (_Lock)
			{
				return A_AddOrReplaceItem(P_Entity);
			}
		}

		public void Remove(IEntityLink<TEntityKey> P_Entity)
		{
			lock (_Lock)
			{
				_Remove(P_Entity);
			}
		}

		public IEnumerator<IEntityLink<TEntityKey>> GetEnumerator()
		{
			lock (_Lock)
			{
				_EnsureFullLoad();
				foreach (IEntityLink<TEntityKey> L_Item in _PK)
				{
					if (Object.Equals(_Purify(L_Item), L_Item))
						yield return L_Item;
				}
			}
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		private void _Remove(IEntityLink<TEntityKey> P_Entity)
		{
			// remove
			_PK.Remove(P_Entity);
			_LeftIndex.Remove(P_Entity);
			_RightIndex.Register(P_Entity);

			Type L_Type = typeof(IEntityLink<TEntityKey>);
			Log.InfoFormat("Removing entity link {0}-{1} of type {2}", P_Entity.Left, P_Entity.Right, L_Type.Name);
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, P_Entity));

		}

		private IEntityLink<TEntityKey> _Purify(IEntityLink<TEntityKey> P_Class)
		{
			if (_LeftIndex.IsInvalidated(P_Class.Left) || _RightIndex.IsInvalidated(P_Class.Right))
				return null;
			else
				return P_Class;
		}

		private bool _IsLoaded(IEntityLink<TEntityKey> P_Entity)
		{
			return _PK.IsLoaded(P_Entity) && Object.Equals(_Purify(P_Entity), P_Entity);
		}

		private bool A_AddOrReplaceItem(IEntityLink<TEntityKey> P_Entity)
		{
			bool L_AlreadyPresent;
			NotifyCollectionChangedEventArgs e = null;

			L_AlreadyPresent = _IsLoaded(P_Entity);
			if (!L_AlreadyPresent)
			{
				e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, P_Entity);
			}

			// registering
			_IndexItem(P_Entity);

			OnCollectionChanged(e);
			return !L_AlreadyPresent;
		}

		private void _IndexItem(IEntityLink<TEntityKey> P_Entity)
		{
			_PK.Register(P_Entity);
			_LeftIndex.Register(P_Entity);
			_RightIndex.Register(P_Entity);
		}

		public void InvalidateLeftKey(TEntityKey P_FkValue)
		{
			lock (_Lock)
			{
				_LeftIndex.Invalidate(P_FkValue);
				_Loaded = false;
			}
		}

		public void InvalidateRightKey(TEntityKey P_FkValue)
		{
			lock (_Lock)
			{
				_RightIndex.Invalidate(P_FkValue);
				_Loaded = false;
			}
		}

		/// <summary>
		/// Check or load all.
		/// </summary>
		private void _EnsureFullLoad()
		{
			// global load if not loaded
			if (!_Loaded)
			{
				foreach (IEntityLink<TEntityKey> L_Entity in _FullLoader())
				{
					_IndexItem(L_Entity);
				}

				_Loaded = true;
				OnCollectionChanged(_ResetArgs);
			}
		}

		/// <summary>
		/// Checks (and load if not) if the entities with the specified value on the specified member are loaded.
		/// </summary>
		/// <param name="P_Fkvalue">The fkvalue</param>
		private void _EnsureLeftLoaded(TEntityKey P_Fkvalue)
		{
			// check if the container is full loaded
			if (!(_Loaded || _LeftIndex.IsLoaded(P_Fkvalue)))
			{
				// check if a the key is loaded and if a specific loader is available
				if (!_LeftIndex.IsLoaded(P_Fkvalue))
				{
					try
					{
						IEnumerable<IEntityLink<TEntityKey>> _Entities = _LeftLoader(P_Fkvalue);
						foreach (IEntityLink<TEntityKey> L_Entity in _Entities)
						{
							_IndexItem(L_Entity);
						}
						_LeftIndex.MarkAsLoaded(P_Fkvalue);

						OnCollectionChanged(_ResetArgs);
					}
					catch (Exception ex)
					{
						throw BuildError(_Config.LoadingErrorCode, ex);
					}
				}
				else
				{
					// no specific loader defined
					_EnsureFullLoad();
				}
			}
		}

		private void _EnsureRightLoaded(TEntityKey P_Fkvalue)
		{
			// check if the container is full loaded
			if (!(_Loaded || _RightIndex.IsLoaded(P_Fkvalue)))
			{
				// check if a the key is loaded and if a specific loader is available
				if (!_LeftIndex.IsLoaded(P_Fkvalue))
				{
					try
					{
						IEnumerable<IEntityLink<TEntityKey>> _Entities = _RightLoader(P_Fkvalue);
						foreach (IEntityLink<TEntityKey> L_Entity in _Entities)
						{
							_IndexItem(L_Entity);
						}
						_LeftIndex.MarkAsLoaded(P_Fkvalue);

						OnCollectionChanged(_ResetArgs);
					}
					catch (Exception ex)
					{
						throw BuildError(_Config.LoadingErrorCode, ex);
					}
				}
				else
				{
					// no specific loader defined
					_EnsureFullLoad();
				}
			}
		}

		#region Events
		#region Collection Changed
		private readonly object CollectionChangedEventLock = new object();
		private NotifyCollectionChangedEventHandler CollectionChangedEvent;

		public event NotifyCollectionChangedEventHandler CollectionChanged
		{
			add
			{
				lock (CollectionChangedEventLock)
				{
					CollectionChangedEvent += value;
				}
			}
			remove
			{
				lock (CollectionChangedEventLock)
				{
					CollectionChangedEvent -= value;
				}
			}
		}

		protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
		{
			NotifyCollectionChangedEventHandler handler = null;

			lock (CollectionChangedEventLock)
			{
				handler = CollectionChangedEvent;

				if (handler == null)
					return;
			}

			handler(this, e);
		}

		#endregion
		#endregion
	}
}
